package cn.sciento.boot.data.change.expand;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.RowBounds;
import cn.sciento.boot.data.change.dto.InsertType;
import cn.sciento.boot.data.change.dto.MybatisInvocation;
import cn.sciento.boot.data.change.intercept.DataChangeInterceptor;
import cn.sciento.boot.data.change.utils.FieldUtil;
import cn.sciento.boot.data.change.utils.JsqlParserUtil;
import cn.sciento.boot.data.change.utils.MappedStatementUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SupportForBatchInsert {
  private static final Logger LOGGER = LoggerFactory.getLogger(DataChangeInterceptor.class);
  
  public static final String REWRITING_PARAM_KEY = "collection";
  
  public static final String DEFAULT_KEY = "param";
  
  public static final String KEY_GENERATOR = "keyGenerator";
  
  public static final String KEY_PROPERTIES = "keyProperties";
  
  public static void doSupport(MybatisInvocation mybatisInvocation) throws SQLException {
    if (mybatisInvocation.getMappedStatement().getKeyGenerator() != null && 
      !(mybatisInvocation.getMappedStatement().getKeyGenerator() instanceof org.apache.ibatis.executor.keygen.NoKeyGenerator)) {
      LOGGER.debug("nothing need to do for it had opened keyGenerator");
      return;
    } 
    LOGGER.debug("support for batchInsert for :{}", mybatisInvocation.getTable().getName());
    mybatisInvocation.setParamRewritedFlag(Boolean.TRUE);
    Object newParameterObject = reBuildParameterObject(mybatisInvocation);
    FieldUtil.changeField(mybatisInvocation.getMappedStatement(), new Jdbc3KeyGenerator(), "keyGenerator");
    FieldUtil.changeField(mybatisInvocation.getMappedStatement(), new Jdbc3KeyGenerator(), "keyGenerator");
    FieldUtil.changeField(mybatisInvocation.getMappedStatement(), mybatisInvocation.getTable().getKeyProperties(), "keyProperties");
    mybatisInvocation.getInvocation().getArgs()[1] = newParameterObject;
  }
  
  private static Object reBuildParameterObject(MybatisInvocation mybatisInvocation) throws SQLException {
    Object paramList = buildRewritingParam(mybatisInvocation);
    Object parameterObject = mybatisInvocation.getBoundSql().getParameterObject();
    if (parameterObject == null)
      parameterObject = (Object)new HashMap<>(1);
    if (parameterObject instanceof Map) {
      ((Map)parameterObject).put("collection", paramList);
    } else if (isBasicType(parameterObject).booleanValue()) {
      List<ParameterMapping> parameterMappings = mybatisInvocation.getBoundSql().getParameterMappings();
      String property = "param";
      if (CollectionUtils.isNotEmpty(parameterMappings))
        property = mybatisInvocation.getBoundSql().getParameterMappings().get(0).getProperty();
      Object tempParameterObject = parameterObject;
      parameterObject = new HashMap<>(1);
      ((Map)parameterObject).put(property, tempParameterObject);
      ((Map)parameterObject).put("collection", paramList);
    } else {
      parameterObject =FieldUtil.objectToMap(parameterObject);
      ((Map)parameterObject).put("collection", paramList);
    } 
    return parameterObject;
  }
  
  private static Object buildRewritingParam(MybatisInvocation mybatisInvocation) throws SQLException {
    Integer count;
    InsertType insertType = mybatisInvocation.getSqlParserInfo().getInsertType();
    LOGGER.debug("===>insertType: {}", insertType);
    if (!InsertType.BATCH_BY_SELECT.equals(insertType)) {
      count = mybatisInvocation.getSqlParserInfo().getItemsListSize();
    } else {
      count = queryCountOfSelectData(mybatisInvocation);
    } 
    String[] keyProperties = mybatisInvocation.getTable().getKeyProperties();
    if (keyProperties == null || keyProperties.length == 0 || count.intValue() == 0)
      return null; 
    List<Object> paramList = new ArrayList();
    for (int i = 0; i < count.intValue(); i++) {
      Map<String, Object> param = new HashMap<>(1);
      param.put(keyProperties[0], null);
      paramList.add(param);
    } 
    return paramList;
  }
  
  private static Boolean isBasicType(Object parameter) {
    Class<?> className = parameter.getClass();
    if (className.equals(Integer.class) || className.equals(Byte.class) || className.equals(Long.class) || className
      .equals(Double.class) || className.equals(Float.class) || className
      .equals(Character.class) || className.equals(Short.class) || className
      .equals(Boolean.class) || className.equals(String.class))
      return Boolean.TRUE;
    return Boolean.FALSE;
  }
  
  private static Integer queryCountOfSelectData(MybatisInvocation mybatisInvocation) throws SQLException {
    String selectSql = mybatisInvocation.getSqlParserInfo().getSelect().toString();
    List<ParameterMapping> parameterMappings = mybatisInvocation.getBoundSql().getParameterMappings();
    BoundSql queryBoundSql = JsqlParserUtil.buildSelectBoundSql(mybatisInvocation, parameterMappings, selectSql);
    MappedStatement selectMappedStatement = MappedStatementUtil.newHashMapMappedStatement(mybatisInvocation.getMappedStatement());
    Object queryResultList = mybatisInvocation.getExecutor().query(selectMappedStatement, mybatisInvocation
        .getParameter(), RowBounds.DEFAULT, null, null, queryBoundSql);
    if (null == queryResultList)
      return 0;
    return ((ArrayList) queryResultList).size();
  }
}
